---
title: 多用组合少用继承
description: 组合优于继承，多用组合少用继承
---


在面向对象编程中，有一条非常经典的设计原则，“组合优于继承，多用组合少用继承”

## 为什么不推荐使用继承?
继承是面向对象四大特性之一， 用来表示类之间的 is-a 关系，可以解决代码复用的问题。但是继承层次过深，过复杂，也会影响到代码的可维护性。

继承最大的问题在于：继承层次过深、继承关系过于复杂会影响到代码的可读性和可维护性。这也是为什么不推荐使用继承。

## 组合相比继承的优势
可以利用组合（composition）、接口、委托（delegation）三个技术手段，一块来解决刚刚继承存在的问题。


接口表示具有某种行为特性。针对会飞这样一个行为特性，可以定义一个 Flyable 接口，只让会飞的鸟去实现这个接口。对于会叫、会下蛋这些行为特性，可以类似地定义 Tweetable 接口、EggLayable 接口。


```java
public interface Flyable {
    void fly();
}
public interface Tweetable {
    void tweet();
}
public interface EggLayable {
    void layEgg();
}
public class Ostrich implements Tweetable,EggLayable {
    @Override
    public void tweet() { //... }
    @Override
    public void layEgg() { //... }
}
```
不过，接口只定义方法，不定义实现。也就是说，每个会下蛋的鸟都要实现一遍 layEgg() 方法，并且实现逻辑是一样的，这就会导致代码重复的问题。

此时可以针对三个接口再定义三个实现类，它们分别是：实现了 fly() 方法的 FlyAbility 类，实现了 tweet() 方法的 TweetAbility 类，实现了 EggLayAbility 类。然后，通过组合和委托技术来消除代码重复。具体的代码实现如下所示：

```java
public class FlyAbility implements Flyable {
    @Override
    public void fly() { // ... }
}

public class Ostrich implements Tweetable ,EggLayable {
    private TweetAbility tweetAbility = new TweetAbility();// 组合
    private EggAbility eggLayAbility = new EggLayAbility(); // 组合
    @Override
    public void tweet() {
        tweetAbility.tweet(); // 委托
    }
    public void layEgg() {
        tweetAbility.layEgg(); // 委托
    }
}
```

继承主要有三个作用，表示 is-a 关系，支持多态特性，代码复用。这三个作用都可以通过其它技术手段来达成。比如 is-a 关系，可以通过组合和接口的 has-a 关系来替代；多态特性可以利用接口来实现；代码复用可以通过组合和委托来实现。
所以，从理论上讲，通过组合、接口、委托三个技术手段，可以完全替换掉继承，在项目中不用或者少用继承，特别是一些复杂的继承关系。

:::tip
Java8 之后接口可以有 default 方法，只需要接口 + default 方法就可以实现委托方法了。
:::
## 如何判断该用组合还是继承
 继承并非一无是处，继承改写成组合意味着要做更细粒度的类的拆分。类和接口的数量会变多，或多或少地会增加代码的复杂程度和维护成本。

所以，在实际的项目开发中，还是要根据具体的情况，来具体选择该用继承还是组合。


如果类之间的继承结构稳定（不会轻易改变），继承层次比较浅（比如最多有两层继承关系），继承关系不复杂，就可以大胆地使用继承。

反之，系统越不稳定，继承层次很深、继承关系复杂，就尽量使用组合来替代继承。


除此之外，还有一些设计模式会固定使用继承和组合。比如，装饰者模式、策略模式、组合模式等都使用了组合关系，而模板模式使用了继承关系。

还有一些特殊的场景要求必须使用继承。如果不能改变一个函数的入参类型，而入参又非接口，为了支持多态，只能采用继承来实现。

```java
public class FeignClient {
    public void encode(String url) {//...}
}
public void demofunction(FeignClient feignClient) {
    feignClient.encode(url);
}
public class CustomizedFeignClient extends FeignClient {
    @Override
    public void encode(String url) { // ... 重写 encode 的实现}
}
// 调用
FeignClient client = new CustomizedFeignClient();
demofunction(client);

```






